//
//  BoundaryHandler.cpp
//  NSESolver
//
//  Created by Patrick Zulian on 3/29/11.
//  Copyright 2011 ICS - USI - Lugano.  All rights reserved.
//

#include "Base.h"


#include "BoundaryHandler.h"
#include "CFDProblem.h"

namespace nsesolver {
    
    
    BoundaryHandler::BoundaryHandler(const CartesianTopology &cart, const Obstacle & obstacle)
    :uBoundary(), vBoundary(), cart(cart), bufferSpace( new BufferSpace< double >(16) ), obstacle(obstacle)
    {
        uBoundary[West]=getOrthogonalValue;
        uBoundary[East]=getOrthogonalValue;
        
        uBoundary[North]=getLeftRightValueNoSlip;
        uBoundary[South]=getLeftRightValueNoSlip;
        
        
        vBoundary[North]=getOrthogonalValue;
        vBoundary[South]=getOrthogonalValue;
        
        vBoundary[East]=getLeftRightValueNoSlip;
        vBoundary[West]=getLeftRightValueNoSlip;
    }
    
    BoundaryHandler::~BoundaryHandler()
    {}
    
    
    void BoundaryHandler::setUBoundaryType(CardinalDirections dir, boundaryValGetter getter)
    {
        uBoundary[dir]=getter;
    }
    
    void BoundaryHandler::setVBoundaryType(CardinalDirections dir, boundaryValGetter getter)
    {
        vBoundary[dir]=getter;
    }
    
    void BoundaryHandler::setUBoundaryType(CardinalDirections dir, BoundaryConditionType type)
    {
        
        NSE_SOLVER_ASSERT( dir != West );
        NSE_SOLVER_ASSERT( dir != East );
        
        switch (type) {
            case NoSlip:
                uBoundary[dir]=getLeftRightValueNoSlip;break;
            case Slip:
                uBoundary[dir]=getLeftRightValueSlip; break;
            case MovingBelt:
                uBoundary[dir]=getLeftRightValueBelt; break;
            default:
                assert(false);
                break;
        }
    }
    
    void BoundaryHandler::setVInflowOutflow(boundaryValGetter inflow)
    {
       // NSE_SOLVER_ASSERT( inflow(1) > 0 );
        vBoundary[North] = inflow; 
        vBoundary[South] = getOutflowValue; 
        setVBoundaryType(West, Slip);
        setVBoundaryType(East, Slip);
    }
    
    
    void BoundaryHandler::setUInflowOutflow(boundaryValGetter inflow)
    {
        //NSE_SOLVER_ASSERT( inflow(1) > 0 );
        uBoundary[West] = inflow;
        uBoundary[East] = getOutflowValue;
        setUBoundaryType(North, Slip);
        setUBoundaryType(South, Slip);
    }
    
    void BoundaryHandler::setVBoundaryType(CardinalDirections dir, BoundaryConditionType type)
    {
        
        NSE_SOLVER_ASSERT( dir != North );
        NSE_SOLVER_ASSERT( dir != South );
        
        switch (type) {
            case NoSlip:
                vBoundary[dir]=getLeftRightValueNoSlip;break;
            case Slip:
                vBoundary[dir]=getLeftRightValueSlip; break;
            case MovingBelt:
                vBoundary[dir]= getLeftRightValueBelt; break;               
            default:
                assert(false);
                break;
        }
    }
    
    
    void BoundaryHandler::uvSendRecv(CardinalDirections cardDir, const MatrixHandle< Matrixd > &uh, const MatrixHandle< Matrixd > &vh)
    {
        
        Communicator &comm( cart.getCommunicator() );
        BufferId uSend, vSend, uRecv, vRecv;
        int dim(0), dir(0);
        
        
        getDimDir(cardDir, &dim, &dir);
        
        
        switch (cardDir) {
            case West:
                uSend = UWest;
                vSend = VWest;
                uRecv = UEast;
                vRecv = VEast;
                break;
            case East:
                uSend = UEast;
                vSend = VEast;
                uRecv = UWest;
                vRecv = VWest;
                break;
            case North:
                uSend = UNorth;
                vSend = VNorth;
                uRecv = USouth;
                vRecv = VSouth;
                break;
            case South:
                uSend = USouth;
                vSend = VSouth;
                uRecv = UNorth;
                vRecv = VNorth;
                break;
        }
        
        bufferSpace->reAllocateBuffer(uSend, uh.size());
        bufferSpace->reAllocateBuffer(uSend + Recv, uh.size());
                
        packData((*bufferSpace)[uSend], uh);

        
        comm.iSend(&(*bufferSpace)[uSend][0], uh.size(), cart.shift(dim, dir), uRecv);
        comm.iRecv(&(*bufferSpace)[uSend + Recv][0], uh.size(), cart.shift(dim, dir), uSend);
        
        
        bufferSpace->reAllocateBuffer(vSend, vh.size());
        bufferSpace->reAllocateBuffer(vSend + Recv, vh.size());
        
        packData((*bufferSpace)[vSend], vh);


        
        comm.iSend(&(*bufferSpace)[vSend][0], vh.size(), cart.shift(dim, dir), vRecv);
        comm.iRecv(&(*bufferSpace)[vSend + Recv][0], vh.size(), cart.shift(dim, dir), vSend);
    }
    
    void BoundaryHandler::pSendRecv(CardinalDirections cardDir, const MatrixHandle< Matrixd > & ph)
    {
        
        Communicator & comm( cart.getCommunicator() );
        BufferId send, recv;
        int dim(0), dir(0);
        
        getDimDir(cardDir, &dim, &dir);
        
        switch (cardDir) {
            case West:
                send = PWest;
                recv = PEast;
                break;
            case East:
                send = PEast;
                recv = PWest;
                break;
            case North:
                send = PNorth;
                recv = PSouth;
                break;
            case South:
                send = PSouth;
                recv = PNorth;
                break;
        }
        
        bufferSpace->reAllocateBuffer(send, ph.size());
        bufferSpace->reAllocateBuffer(send + Recv, ph.size());
        

        packData((*bufferSpace)[send], ph);
        
        comm.iSend(&(*bufferSpace)[send][0], ph.size(), cart.shift(dim, dir), recv);
        comm.iRecv(&(*bufferSpace)[send + Recv][0], ph.size(), cart.shift(dim, dir), send);
    }
    
    void BoundaryHandler::setBoundary(CFDProblem &problem)
    {
        
        Matrixd &u(problem.U());
        Matrixd &v(problem.V());
        const int imax = problem.IMax();
        const int jmax = problem.JMax();
        
        if (isBoundary(West, cart)) {
            for(int j=0;j<=jmax+1;++j) {   
                u(0,j) = (*uBoundary[West])(u(1,j));
            }
            
            for(int j=1;j<=jmax;++j){
                v(0,j) = (*vBoundary[West])(v(1,j));
            }
        } else {
            uvSendRecv(West, u(Range(1, 2), Range(0, jmax+2)),  v(Range(1, 2), Range(1, jmax+1)));
        }
        
        
        if (isBoundary(East, cart)) {
            for(int j=0;j<=jmax+1;++j) {  
                u(imax,j) = (*uBoundary[East])(u(imax-1, j));
            }
            
            for(int j=1;j<=jmax;++j){
                v(imax+1, j) = (*vBoundary[East])(v(imax, j));
            }
        } else {
            uvSendRecv(East, u(Range(imax-1, imax), Range(0, jmax+2)),  v(Range(imax, imax+1), Range(1, jmax+1)));
        }
        
        
        if (isBoundary(South, cart)) {            
            for(int i=1;i<=imax;++i) {
                u(i,0) = (*uBoundary[South])(u(i,1));
            }
            
            for(int i=0;i<=imax+1;++i) {
                v(i,0) = (*vBoundary[South])(v(i,1));
            }
        } else {
            uvSendRecv(South, u(Range(1, imax+1), Range(1, 2)), v(Range(0, imax+2), Range(1, 2)));
        }
        
        
        if (isBoundary(North, cart)) {            
            for(int i=1;i<=imax;++i) {
                u(i, jmax+1) = (*uBoundary[North])(u(i, jmax));
            }
            
            for(int i=0;i<=imax+1;++i) {
                v(i,jmax) = (*vBoundary[North])(v(i, jmax-1));
            }
        } else {
            uvSendRecv(North, u(Range(1, imax+1), Range(jmax, jmax+1)), v(Range(0, imax + 2), Range(jmax-1, jmax)));
        }
        
        
        uvUpdate(u, v, imax, jmax);
        
        obstacle.setBoundary(problem);
    }
    
    void BoundaryHandler::uvUpdate(Matrixd &u, Matrixd &v, const int imax, const int jmax)
    {
        Communicator & comm( cart.getCommunicator() );
        comm.waitAll();
        
        if (!isBoundary(West, cart)) {
            unpackData((*bufferSpace)[Recv + UWest], u(Range(0, 1), Range(0, jmax+2)));
            unpackData((*bufferSpace)[Recv + VWest],  v(Range(0, 1), Range(1, jmax+1)));            
        }
        
        if (!isBoundary(East, cart)) {
            unpackData((*bufferSpace)[Recv + UEast], u(Range(imax, imax+1), Range(0, jmax+2)));
            unpackData((*bufferSpace)[Recv + VEast], v(Range(imax+1, imax+2), Range(1, jmax+1)));

        }
        
        
        if (!isBoundary(South, cart)) {
            unpackData((*bufferSpace)[Recv + USouth], u(Range(1, imax+1), Range(0, 1)));
            unpackData((*bufferSpace)[Recv + VSouth], v(Range(0, imax+2), Range(0, 1)));            
        }
        
        if (!isBoundary(North, cart)) {
            unpackData((*bufferSpace)[Recv + UNorth], u(Range(1, imax+1), Range(jmax+1, jmax+2)));
            unpackData((*bufferSpace)[Recv + VNorth], v(Range(0, imax + 2), Range(jmax, jmax+1)));
        }
    }
    
    void BoundaryHandler::pUpdate(Matrixd &p, const int imax, const int jmax)
    {
        Communicator & comm( cart.getCommunicator() );
        comm.waitAll();
        
        if (!isBoundary(West, cart)) {
            unpackData((*bufferSpace)[Recv + PWest], p(Range(0,1),Range(1,jmax+1)));
        }
        
        if (!isBoundary(East, cart)) 
        {
            unpackData((*bufferSpace)[Recv + PEast], p(Range(imax+1,imax+2),Range(1,jmax+1)));
        }
        
        if (!isBoundary(South, cart)) 
        {
            unpackData((*bufferSpace)[Recv + PSouth], p(Range(1,imax+1),Range(0,1)));
        }
        
        if (!isBoundary(North, cart))
        {
            unpackData((*bufferSpace)[Recv + PNorth], p(Range(1,imax+1),Range(jmax+1,jmax+2)));
        }
    }
    
    void BoundaryHandler::setPBoundary(CFDProblem &problem)
    {
        
        Matrixd &p(problem.P());
        const int imax = problem.IMax();
        const int jmax = problem.JMax();
        
        if (isBoundary(West,cart)) {
            for(int j=1;j<=jmax;++j) {
                p(0,j)= p(1,j);
            }
        } else{
            pSendRecv(West, p(Range(1,2),Range(1,jmax+1)));
        }
        
        
        if (isBoundary(East,cart)) {
            for(int j=1;j<=jmax;++j) {
                p(imax+1,j)= p(imax,j);
            }
        } else {
            pSendRecv(East, p(Range(imax,imax+1),Range(1,jmax+1)));
        }
        
        
        if (isBoundary(South,cart)) {
            for(int i=0;i<=imax+1;++i) { //! + 1 - 1
                p(i,0)= p(i,1);
            }
        } else {
            pSendRecv(South, p(Range(1,imax+1),Range(1,2)));
        }
        
        
        if (isBoundary(North,cart)) {
            for(int i=1;i<=imax;++i) {
                p(i,jmax+1)= p(i,jmax);
            }
        } else {
            pSendRecv(North, p(Range(1,imax+1),Range(jmax,jmax+1)));
        }
        
        
        pUpdate(p, imax, jmax);
        
        obstacle.setPBoundary(problem);
    }
    
    
    
    void BoundaryHandler::setFGBoundary(Matrixd &F, Matrixd &G, const CFDProblem & problem)
    {
        
        const Matrixd & u(problem.U());
        const Matrixd & v(problem.V());
        
        //set the boundaries on F, G by means of (35)
        for(int j=1;j<= problem.JMax()+1;++j)
        {
            F(0,j) = u(0,j);
            F(problem.IMax(), j) = u(problem.IMax(), j);
        }
        
        for(int i=1;i<= problem.IMax()+1;++i)
        {
            G(i,0) = v(i,0);
            G(i, problem.JMax()) = v(i, problem.JMax());
        }
        
        
        obstacle.setFGBoundary(F, G, problem);
    }
}




